/*
 * Main.scala
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package csvutils

import scala.io.Source
import scala.collection.mutable.{HashMap}

import java.io.{BufferedWriter,FileWriter}

object Main {

    /**
     * @param args the command line arguments
     */
    def main(args: Array[String]) :Unit = {

        //internal functions
        //top
        lazy val top_column:Int=args(4).toInt
        lazy val top_sortColumn:Int=args(5).toInt
        lazy val top_n:Int= args(6).toInt
        lazy val desc:Boolean = args(2).equals("Desc")

        def top_mergeStrict(depth:Int,toInsert:sortNumberObject,old:List[sortNumberObject]):List[sortNumberObject] = {
            if(old.size==0){
                List[sortNumberObject](toInsert)
            }else{
                depth match{
                    case 0 => List[sortNumberObject]()
                    case 1 => List[sortNumberObject](if (toInsert.value!=old.head.value &&(toInsert.value>old.head.value)==desc)toInsert else old.head)
                    case _ => old match {
                            case head::tail if((toInsert.value>head.value)==desc &&toInsert.value!=head.value) => toInsert::head::tail.slice(0,depth-2)
                            case head::tail => head::top_mergeStrict(depth-1, toInsert, tail)
                            case _ =>
                                List[sortNumberObject](toInsert)
                        }
                }
            }
        }

        def top_mergePlace(depth:Int,toInsert:sortNumberObject,old:List[sortNumberObject]):List[sortNumberObject]={
            if(old.size==0){
                if(depth>0){
                    List[sortNumberObject](toInsert)
                }else{
                    List[sortNumberObject]()
                }
            }else{
                depth match{
                    case 1 =>
                        if((toInsert.value>old.head.value)==desc||toInsert.value==old.head.value){
                            //                            Console.err.println(toInsert.value+sep+old.head.value)
                            toInsert::old.filter(_.value==toInsert.value)
                        }else{
                            old.filter(_.value==old.head.value)
                        }
                    case _ => 
                        old match {
                            case head::tail if(toInsert.value==head.value||(toInsert.value>head.value)==desc ) =>
//                                Console.err.println(toInsert.value)
                                val tmp =toInsert::head::tail.slice(0,depth-2)
                                val s=tail.drop(depth-2)
                                //                                if (s>0)Console.err.println(s)
                                
                                //                                Console.err.println(tmp.size+sep+s.size+sep+depth)
                                //                                if(s.size>0) Console.err.println(toInsert.value+sep+s.head.value)
                                tmp:::tail.drop(depth-2).filter(_.value==tmp.last.value)
                            case head::tail =>
//                                Console.err.println("err")
                                head::top_mergePlace(depth-1,toInsert,tail)
                            case _ =>
                                List[sortNumberObject](toInsert)
                        }
                }
            }
        }


        //parse arguments
        args.toList match{

            case empty if (empty.size==0) => println(getHelp)
            case "top" ::"Strict" :: "Asc":: tail if(tail.size>2) => top(false,tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt,top_mergeStrict)
            case "top" ::"Strict" :: "Desc":: tail if(tail.size>2) => top(true,tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt,top_mergeStrict)
            case "top" ::"Place" :: "Asc":: tail if(tail.size>2) => top(false,tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt,top_mergePlace)
            case "top" ::"Place" :: "Desc":: tail if(tail.size>2) => top(true,tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt,top_mergePlace)
            case "toMetis" ::tail if(tail.size>2) => toMetis(tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt,tail(4).toInt,tail(5))
            case "mergeInto" :: tail if (tail.size>3) => mergeInto(tail(0),tail(1).toInt,tail(2),tail(3).toInt)
            case "generateTriplets" :: tail if (tail.size>3) => generateTriplets(tail(0),tail(1).toInt,tail(2).toInt,tail(3).toInt)
            case _ => Console.err.println(getHelp)
        }
    }

    val getHelp= """Usage: csvUtilsScala <cmd> <input>
where <cmd> can be:
top - extracts the top n results for column refColumn with regard to column sortColumn in a tab-separated csv file. Column sortColumn must hold numbers. Column count starts with 0
    <input> : <Strict/Place> <Asc/Desc> <csvFile> <refColumn> <sortColumn> <n>
toMetis - reads a csvfile with pairs in column a and b and a number in column weight. Prints an infile for hmetis with integer-weights scaled by the scale factor.
    <input> : <csvfile <a> <b> <weight column> <scale factor> <outPrefix>
mergeInto - makes a complete join on the two files with regard to the two columns. Column count starts on 0
    <input> : <csvfile1> <coulumn1> <csvfile2> <column2>
generateTriplets - generates triplets according to the neareast neighbor principle. Assumes a similarity search file with query in column q, target in column t and a score in column s
    <input> : <csvfile> <column q> <column t> <column s>
"""
    val sep="\t"

    def generateTriplets(inFile:String,columnQ:Int,columnT:Int,columnS:Int):Unit={
        var map:Map[String,Double]= Map()
        var keys:List[String]=List[String]()
        for( line <- Source.fromFile(inFile).getLines){
            val l:List[String]=line.slice(0,line.size-1).split("\t").toList
            if (!keys.exists(_==l(columnQ))){
                keys= l(columnQ)::keys
            }
            if (!keys.exists(_==l(columnT))){
                keys= l(columnT)::keys
            }
            val score=l(columnS).toDouble
            val key=l(columnQ)+"¤"+l(columnT)
            map.get(key) match{
                case Some(s:Double) if (score>s) => map+= (key->score)
                case Some(s:Double) =>
                case None => map+= (key->score)
                case _ => Console.err.println("trying to do strange things to the map")
            }
        }
        Console.err.println(keys.size)
        //create triplets
        for (i <- 0 to keys.size -1){
            for (j <- 0 to i-1){
                for (k <- 0 to j-1){
                    val dist12:Double = map.get(keys(i)+"¤"+keys(j)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val dist13:Double = map.get(keys(i)+"¤"+keys(k)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val dist21:Double = map.get(keys(j)+"¤"+keys(i)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val dist23:Double = map.get(keys(j)+"¤"+keys(k)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val dist31:Double = map.get(keys(k)+"¤"+keys(i)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val dist32:Double = map.get(keys(k)+"¤"+keys(j)) match {
                        case Some(s:Double)=> s
                        case None => 0.0
                    }
                    val avg12:Double= (dist12+dist21)/2
                    val avg13:Double= (dist13+dist31)/2
                    val avg23:Double= (dist23+dist32)/2

                    if (avg12>avg13 && avg12>avg23){
                        // keys i and j togheter and k in the root
                        println(keys(i)+" "+keys(j)+" "+keys(k)+"\t"+Math.min(avg12-avg13,avg12-avg23))
                    }else if ( avg13>avg23){
                        // keys i and k are together and j in the root
                        println(keys(i)+" "+keys(k)+" "+keys(j)+"\t"+Math.min(avg13-avg12,avg13-avg23))
                    }else{
                        // keys j and k are together and i in the root
                        println(keys(j)+" "+keys(k)+" "+keys(i)+"\t"+Math.min(avg23-avg12,avg23-avg13))
                    }
                }
            }
        }
    }

    def mergeInto(inFile1:String,column1:Int,inFile2:String,column2:Int):Unit={
        var map:Map[String,List[List[String]]]= Map()
        //read the second file and store the data in a map
        for(line <- Source.fromFile(inFile2).getLines){
            val l:List[String] = line.slice(0,line.size-1).split("\t").toList
            map.get(l(column2)) match {
                case Some(list:List[List[String]]) =>
                    map+= (l(column2) -> (l::list))
                case None =>
                    map+= (l(column2) -> List[List[String]](l))
                case _ => Console.err.println("trying to add strange things to the map")
            }
        }
        //parse the first file and add data from the map
        for (line <- Source.fromFile(inFile1).getLines){
            val l:List[String]=line.slice(0,line.size-1).split("\t").toList
            map.get(l(column1)) match {
                case Some(list:List[List[String]]) =>
                    println(list.map((x)=>((l:::x).foldLeft("")(_+sep+_).substring(1))).foldLeft("")(_+"\n"+_).substring(1))
                case _ =>
                    println(l.foldLeft("")(_+sep+_).substring(1))
            }
        }
    }

    def toMetis(inFile:String,a:Int,b:Int,weightColumn:Int,scale:Double,outPrefix:String):Unit ={
        var map:Map[String,Int]= Map()
        var all:List[String]=List[String]()
        var count=0
        val outMain:BufferedWriter=new BufferedWriter(new FileWriter(outPrefix+".csv"))
        var linenr=0
        for (line <-Source.fromFile(inFile).getLines){
            linenr+=1
            if(linenr%1000000==0)Console.err.println(linenr)
            val l= line.slice(0,line.size-1).split("\t")
            val id1:Int=map.get(l(a)) match{
                case Some(id) =>
                    id
                case None =>
                    count+= 1
                    map += (l(a)-> count)
                    count
            }
            val id2:Int = map.get(l(b)) match{
                case Some(id) =>
                    id
                case None =>
                    count+=1
                    map += (l(b)-> count)
                    count
            }
            outMain.write(((l(weightColumn).toDouble)*scale).round.toInt+" "+id1+" "+id2+"\n")

//            all=(((l(weightColumn).toDouble)*scale).round.toInt+" "+id1+" "+id2+"\n")::all
        }
//        outMain.write(all.size+" "+map.size+" 1\n")
//        for (line<-all){
//            outMain.write(line)
//        }
        outMain.close
        Console.err.println("Prepend: \n"+linenr+" "+map.size+" 1\n"+"to "+outPrefix+".csv\n")
        val outMapping:BufferedWriter= new BufferedWriter(new FileWriter(outPrefix+"_mapping.csv"))
        for (key <- map.keys){
            outMapping.write(key+sep+map.get(key).get+"\n")
        }
        outMapping.close()
    }

    def top(desc:Boolean,inFile:String,column:Int,sortColumn:Int,n:Int, merge:(Int,sortNumberObject,List[sortNumberObject])=>List[sortNumberObject]):Unit ={
        var map:Map[String,List[sortNumberObject]] = Map[String,List[sortNumberObject]]()
        var limits:Map[String,Double] = Map[String,Double]()
        var linenr=0
        for ( line <- Source.fromFile(inFile).getLines){
            linenr+=1
            if(linenr%100000==0) Console.err.println(linenr)
            val l= line.slice(0,line.size-1).split("\t")
            val so:sortNumberObject=new sortNumberObject(line.slice(0,line.size-1),l(column),l(sortColumn).toDouble)
            limits.get(so.key) match{
                case Some(limit) if(so.value==limit||(so.value>limit)==desc ||map.get(so.key).get.size<n)=>
                    val nList= merge(n,so,map.get(so.key).get)
                    //                    if(so.value==limit&&map.get(so.key).get.size+1==nList.size){
                    //                        Console.err.println(nList.size)
                    //                    }
                    //                    Console.err.println(map.get(so.key).get.size)
                    //                    Console.err.println(nList.foldRight("")(_.value+sep+_))
                    limits+=(so.key->nList.last.value)
                    map+=(so.key-> nList)
                case None =>
                    map+= (so.key -> List[sortNumberObject](so))
                    limits+= (so.key -> so.value)
                case _ =>
            }
        }
        //print
        for (idList<-map.values){
            for (ll<-idList){
                println(ll)
            }
        }
    }


}


